package org.redmagic.parse;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;

import org.redmagic.api.Location;
import org.redmagic.api.MagicParser;
import org.redmagic.location.file.FileLocation;
import org.redmagic.location.web.WebLocation;
import org.redmagic.utils.DocumentLoader;
import org.redmagic.utils.PlaceholderHelper;
import org.redmagic.utils.PropertiesLoader;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class DefaultMagicParser implements MagicParser {

    private static final String HOSTNAME = "HOSTNAME";
    
    public List<Location> parse(String magicConfigFile, List<String> localPropertiesOverride) {
        Properties availableProperties = loadProperties(localPropertiesOverride);
        Document document = DocumentLoader.load(magicConfigFile);
        return parseDocument(document, availableProperties);
    }

    private Properties loadProperties(List<String> localPropertiesOverride) {
        Properties properties = new Properties();
        properties.put(HOSTNAME, getLocalHostname());
        loadLocalPropertiesOverride(localPropertiesOverride, properties);
        return properties;
    }

    private String getLocalHostname() {
        try {
            InetAddress inetAddress = InetAddress.getLocalHost();
            return inetAddress.getHostName();
        } catch (UnknownHostException uhe) {
            throw new RuntimeException(uhe.getMessage(), uhe);
        }
    }
    
    private void loadLocalPropertiesOverride(List<String> localPropertiesOverride, Properties properties) {
        PlaceholderHelper helper = new PlaceholderHelper();
        for (String fileName : localPropertiesOverride) {
            String resolvedFileName = helper.resolve(fileName, properties);
            Properties localProperties = PropertiesLoader.load(resolvedFileName);
            properties.putAll(localProperties);
        }
    }
    
    private List<Location> parseDocument(Document document, Properties properties) {
        List<Location> locations = new ArrayList<Location>();
        Element locationsElement = getLocationsElement(document);
        List<Element> locationElements = getChildElements(locationsElement);
        for (Element locationElement : locationElements) {
            locations.add(parseLocationElement(locationElement, properties));
        }
        return locations;
    }

    private Element getLocationsElement(Document document) {
        NodeList nodeList = document.getElementsByTagName("locations");
        if (nodeList.getLength() == 1) {
            return (Element) nodeList.item(0);
        } else {
            throw new RuntimeException("Expecting only a single 'locations' element.");
        }
    }
    
    private List<Element> getChildElements(Element element) {
        List<Element> elements = new ArrayList<Element>();
        NodeList nodeList = element.getChildNodes();
        for (int index = 0; index < nodeList.getLength(); index++) {
            Node node = nodeList.item(index);
            if (node.getNodeType() == Node.ELEMENT_NODE) {
                elements.add((Element) node);
            }
        }
        return elements;
    }
    
    private Location parseLocationElement(Element locationElement, Properties properties) {
        PlaceholderHelper helper = new PlaceholderHelper();
        String tagName = locationElement.getTagName();
        if ("commandLineLocation".equals(tagName)) {
            throw new UnsupportedOperationException();
        } else if ("fileLocation".equals(tagName)) {
            String uri = locationElement.getAttribute("uri");
            uri = helper.resolve(uri, properties);
            return new FileLocation(uri);
        } else if ("webLocation".equals(tagName)) {
            String uri = locationElement.getAttribute("uri");
            uri = helper.resolve(uri, properties);
            List<List<String>> attempts = parseAttempts(getChildElements(locationElement), properties);
            return new WebLocation(uri, attempts);
        } else {
            throw new RuntimeException("Unable to parse location with name: " + tagName);
        }
    }

    private List<List<String>> parseAttempts(List<Element> attemptElements, Properties properties) {
        List<List<String>> attempts = new ArrayList<List<String>>();
        if (attemptElements.isEmpty()) {
            List<String> attempt = Arrays.asList("");
            attempts.add(attempt);
        } else {
            for (Element attemptElement : attemptElements) {
                List<Element> tags = getChildElements(attemptElement);
                if (tags.isEmpty()) {
                    List<String> attempt = Arrays.asList("");
                    attempts.add(attempt);
                } else {
                    List<String> attempt = new ArrayList<String>();
                    for (Element tag : tags) {
                        attempt.add(tag.getNodeValue());
                    }
                }
            }
        }
        return attempts;
    }
}
